package com.czg.jsonschema2lombokpojo.rules;

import com.czg.jsonschema2lombokpojo.JsonPointerUtils;
import com.czg.jsonschema2lombokpojo.Schema;

import com.fasterxml.jackson.databind.JsonNode;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JDocCommentable;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JType;
import org.apache.commons.lang.StringUtils;

/**
 * 应用表示属性定义的schema规则。
 *
 * @see <a href=
 * "http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.2">http:/
 * /tools.ietf.org/html/draft-zyp-json-schema-03#section-5.2</a>
 */
public class PropertyRule implements Rule<JDefinedClass, JDefinedClass> {

    private final RuleFactory ruleFactory;

    protected PropertyRule(RuleFactory ruleFactory) {
        this.ruleFactory = ruleFactory;
    }

    /**
     * 应用此schema规则执行所需的代码生成步骤。
     * <p>
     * 该规则根据Java Bean规范向给定的Java类添加属性。向该类添加私有字段以及相应的访问器方法。
     *
     * @param nodeName 要应用的属性的名称
     * @param node     描述此属性特征的节点
     * @param parent   父节点
     * @param jclass   应该添加此属性的Java类
     * @return 给定的jclass
     */
    @Override
    public JDefinedClass apply(String nodeName, JsonNode node, JsonNode parent, JDefinedClass jclass, Schema schema) {
        String propertyName;
        if (StringUtils.isEmpty(nodeName)) {
            propertyName = "__EMPTY__";
        } else {
            propertyName = ruleFactory.getNameHelper().getPropertyName(nodeName, node);
        }

        String pathToProperty;
        if (schema.getId() == null || schema.getId().getFragment() == null) {
            pathToProperty = "#/properties/" + JsonPointerUtils.encodeReferenceToken(nodeName);
        } else {
            pathToProperty = "#" + schema.getId().getFragment() + "/properties/" + JsonPointerUtils.encodeReferenceToken(nodeName);
        }

        Schema propertySchema = ruleFactory.getSchemaStore().create(schema, pathToProperty, ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters());
        JType propertyType = ruleFactory.getSchemaRule().apply(nodeName, node, parent, jclass, propertySchema);
        propertySchema.setJavaTypeIfEmpty(propertyType);


        node = resolveRefs(node, schema);

        int accessModifier =  JMod.PRIVATE;
        JFieldVar field = jclass.field(accessModifier, propertyType, propertyName);

        propertyAnnotations(nodeName, node, schema, field);

        formatAnnotation(field, jclass, node);

        ruleFactory.getAnnotator().propertyField(field, jclass, nodeName, node);


        if (node.has("pattern")) {
            ruleFactory.getPatternRule().apply(nodeName, node.get("pattern"), node, field, schema);
        }

        ruleFactory.getDefaultRule().apply(nodeName, node.get("default"), node, field, schema);

        ruleFactory.getMinimumMaximumRule().apply(nodeName, node, parent, field, schema);

        ruleFactory.getMinItemsMaxItemsRule().apply(nodeName, node, parent, field, schema);

        ruleFactory.getMinLengthMaxLengthRule().apply(nodeName, node, parent, field, schema);

        ruleFactory.getDigitsRule().apply(nodeName, node, parent, field, schema);

        return jclass;
    }

    private boolean hasEnumerated(Schema schema, String arrayFieldName, String nodeName) {
        JsonNode array = schema.getContent().get(arrayFieldName);
        if (array != null) {
            for (JsonNode requiredNode : array) {
                if (nodeName.equals(requiredNode.asText())) {
                    return true;
                }
            }
        }

        return false;
    }

    private boolean hasFlag(JsonNode node, String fieldName) {
        if (node.has(fieldName)) {
            final JsonNode requiredNode = node.get(fieldName);
            return requiredNode.asBoolean();
        }

        return false;
    }

    private boolean isDeclaredAs(String type, String nodeName, JsonNode node, Schema schema) {
        return hasEnumerated(schema, type, nodeName) || hasFlag(node, type);
    }

    private boolean isRequired(String nodeName, JsonNode node, Schema schema) {
        return isDeclaredAs("required", nodeName, node, schema);
    }

    private boolean useOptional(String nodeName, JsonNode node, Schema schema) {
        return isDeclaredAs("javaOptional", nodeName, node, schema);
    }

    private void propertyAnnotations(String nodeName, JsonNode node, Schema schema, JDocCommentable generatedJavaConstruct) {
        if (node.has("title")) {
            ruleFactory.getTitleRule().apply(nodeName, node.get("title"), node, generatedJavaConstruct, schema);
        }

        if (node.has("javaName")) {
            ruleFactory.getJavaNameRule().apply(nodeName, node.get("javaName"), node, generatedJavaConstruct, schema);
        }

        if (node.has("description")) {
            ruleFactory.getDescriptionRule().apply(nodeName, node.get("description"), node, generatedJavaConstruct, schema);
        }

        if (node.has("$comment")) {
            ruleFactory.getCommentRule().apply(nodeName, node.get("$comment"), node, generatedJavaConstruct, schema);
        }

        if (node.has("required")) {
            ruleFactory.getRequiredRule().apply(nodeName, node.get("required"), node, generatedJavaConstruct, schema);
        } else {
            ruleFactory.getNotRequiredRule().apply(nodeName, node.get("required"), node, generatedJavaConstruct, schema);
        }
    }

    private void formatAnnotation(JFieldVar field, JDefinedClass clazz, JsonNode node) {
        String format = node.path("format").asText();
        if ("date-time".equalsIgnoreCase(format)) {
            ruleFactory.getAnnotator().dateTimeField(field, clazz, node);
        } else if ("date".equalsIgnoreCase(format)) {
            ruleFactory.getAnnotator().dateField(field, clazz, node);
        } else if ("time".equalsIgnoreCase(format)) {
            ruleFactory.getAnnotator().timeField(field, clazz, node);
        } else if ("email".equalsIgnoreCase(format) && ruleFactory.getGenerationConfig().isIncludeValidationAnnotations()) {
            if (ruleFactory.getGenerationConfig().isUseJakartaValidation()) {
                field.annotate(jakarta.validation.constraints.Email.class);
            } else {
                field.annotate(javax.validation.constraints.Email.class);
            }
        }
    }

    private JsonNode resolveRefs(JsonNode node, Schema parent) {
        if (node.has("$ref")) {
            Schema refSchema = ruleFactory.getSchemaStore().create(parent, node.get("$ref").asText(), ruleFactory.getGenerationConfig().getRefFragmentPathDelimiters());
            JsonNode refNode = refSchema.getContent();
            return resolveRefs(refNode, refSchema);
        } else {
            return node;
        }
    }

    private boolean isObject(JsonNode node) {
        return node.path("type").asText().equals("object");
    }

    private boolean isArray(JsonNode node) {
        return node.path("type").asText().equals("array");
    }


}
